﻿using OA.Infrastructure.Extensions;

namespace AMS.Services.Impl;

public class LossService : ILossService
{
    private readonly DbContext _context;

    private readonly IApprovalService _approvalService;

    public LossService(DbContext context, IApprovalService approvalService)
    {
        _context = context;

        _approvalService = approvalService;
    }

    public async Task<PageResult<FixedAssetsLoss.QueryModel>> GetPageAsync(FixedAssetsLoss.ConditionSelectorCondition condition, CancellationToken cancellationToken)
    {
        var result = new PageResult<FixedAssetsLoss.QueryModel>();

        var currentUser = _context.CurrentUser();

        var accessExpression = currentUser.Roles.Any(n => n.Name == "admin")
            ? (n => true)
            : (Expression<Func<FixedAssetsLoss, bool>>?)(n => n.Reporter.Id == currentUser.Id);

        try
        {
            var expression = GetConditionSelectorCondition(condition);

            var data = await _context.Set<FixedAssetsLoss>()
                .Include(m => m.Reporter)
                .Include(m => m.LossDetails).ThenInclude(x => x.Assets).ThenInclude(x => x.Category)
                .Include(m => m.LossDetails).ThenInclude(x => x.Assets).ThenInclude(x => x.Supplier)
                .Where(accessExpression)
                .Where(expression)
                .OrderByDescending(x => x.CreatedDate)
                .Skip((condition.PageNum - 1) * condition.PageSize)
                .Take(condition.PageSize)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = data.Select(x => GetQueryModel(x));

            result.PageNum = condition.PageNum;

            result.PageSize = condition.PageSize;

            result.Total = await _context.Set<FixedAssetsLoss>().Where(accessExpression).CountAsync(expression, cancellationToken);

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }


    public async Task<OperationalResult> CreateAsync(FixedAssetsLoss.CreateModel loss, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        var committer = _context.CurrentUser();

        try
        {
            var entity = new FixedAssetsLoss
            {
                Remark = loss.Remark,
                Reporter = _context.CurrentUser(),
            };

            foreach (var lossDetails in loss.LossDetails)
            {
                var fixedAssets = _context.Set<FixedAssets>().Find(new object[] { lossDetails.AssetsId });

                var lossDetail = new FixedAssetsLossDetail
                {
                    Assets = fixedAssets!,
                    Remark = lossDetails.Remark
                };

                entity.LossDetails.Add(lossDetail);
            }

            await _context.Set<FixedAssetsLoss>().AddAsync(entity, cancellationToken);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;

            result.Message = $"{affectedRows} rows affected.";

            _ = await _approvalService.CreateApprovalAsync(committer, entity.Id, ApprovalType.Loss, AssetsType.Durable, loss.Remark, ApprovalService.lossApprovalFlow, cancellationToken);
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    private static FixedAssetsLoss.QueryModel GetQueryModel(FixedAssetsLoss loss)
    {
        return new FixedAssetsLoss.QueryModel
        {
            Id = loss.Id,
            CreatedDate = loss.CreatedDate,
            LastModifiedDate = loss.LastModifiedDate,
            LossDetails = loss.LossDetails.Select(x => LossDetailService.GetQueryModel(x)),
            Remark = loss.Remark,
            Reporter = DefinitionServiceExtensions.GetUserQueryModel(loss.Reporter),
            Status = loss.Status,
        };
    }

    private static Expression<Func<FixedAssetsLoss, bool>> GetConditionSelectorCondition(FixedAssetsLoss.ConditionSelectorCondition condition)
    {
        Expression<Func<FixedAssetsLoss, bool>>? expression = null;

        if (condition.LossStatus is not null)
        {
            Expression<Func<FixedAssetsLoss, bool>>? lossStatus = null;
            lossStatus = loss => condition.LossStatus == loss.Status;
            expression = expression?.And(lossStatus) ?? lossStatus;
        }

        var createDateSelectorCondition = new FixedAssetsLoss.CreateDateSelectorCondition()
        {
            BeginDate = condition.CreateDateSelectorBeginDate,
            EndDate = condition.CreateDateSelectorEndDate,
        };

        var createDateSelectorExpression = GetCreateDateSelectorCondition(createDateSelectorCondition);

        if (createDateSelectorExpression is not null)
        {
            expression = expression?.And(createDateSelectorExpression) ?? createDateSelectorExpression;
        }

        return expression ?? (loss => true);
    }

    private static Expression<Func<FixedAssetsLoss, bool>>? GetCreateDateSelectorCondition(FixedAssetsLoss.CreateDateSelectorCondition condition)
    {
        Expression<Func<FixedAssetsLoss, bool>>? expression = null;

        if (condition.BeginDate is not null)
        {
            Expression<Func<FixedAssetsLoss, bool>>? beginDate = null;
            beginDate = loss => condition.BeginDate == loss.CreatedDate;
            expression = expression?.And(beginDate) ?? beginDate;
        }

        if (condition.EndDate is not null)
        {
            Expression<Func<FixedAssetsLoss, bool>>? endDate = null;
            endDate = loss => condition.EndDate == loss.CreatedDate;
            expression = expression?.And(endDate) ?? endDate;
        }

        return expression;
    }
}